<?php

namespace App\Service;

use App\Entity\CronTabTask;
use App\Entity\Money;
use App\Entity\MoneyCashLog;
use App\Entity\MoneyFreezeLog;
use App\Entity\MoneyLog;
use App\Entity\MoneyPayOrder;
use App\Entity\MoneyType;
use App\Entity\PlatIncome;
use App\Entity\PlatIncomeLog;
use App\Entity\User;
use App\Repository\ConfigRepository;
use App\Repository\CronTabTaskRepository;
use App\Repository\MoneyConfigRepository;
use App\Repository\MoneyFreezeLogRepository;
use App\Repository\MoneyLogRepository;
use App\Repository\MoneyLogTypeRepository;
use App\Repository\MoneyRepository;
use App\Repository\MoneyStaticRepository;
use App\Repository\MoneyTypeRepository;
use App\Repository\PlatIncomeLogRepository;
use App\Repository\PlatIncomeRepository;
use App\Repository\UserRepository;
use App\Service\Wechat\WechatPay;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Common\Persistence\ManagerRegistry;
use function Doctrine\ORM\QueryBuilder;

class MoneyService extends ServiceEntityRepository
{

    //资金账户设置
    const __MONEY_XIAN_JIN__ = 1;
    const __MONEY_INVITE__ = 2;
    const __MONEY_INSURE__ = 2;
    const __MONEY_JI_FEN__ = 3;
    const __MONEY_XU_NI_BI__ = 4;//虚拟货币
    const __MONEY_JING_YAN__ = 5;//用户经验值
    const __MONEY_GU_QUAN__ = 6;//股权分红账户
    //平台flag设置
    const __FLAG_PLAT_INCOME__ = 1;
    const __FLAG_BONUS_POOL__ = 2;
    const __FLAG_AGENT_INCOME__ = 3;

    //冻结类型
    const __FREEZE_GU_QUAN__ = 2;
    const __FREEZE_TI_XIAN__ = 1;


    //日志类型设置


    public $userRepository;
    public $moneyRepository;
    public $moneyLogRepository;
    public $moneyTypeRepository;
    public $moneyConfigRepository;
    public $moneyStaticRepository;
    public $moneyLogTypeRepository;
    public $platIncomeRepository;
    public $platIncomeLogRepository;
    public $configRepository;
    public $cronTabTaskRepository;
    public $moneyFreezeLogRepository;

    public function __construct(
        ManagerRegistry $registry
        , UserRepository $userRepository
        , MoneyRepository $moneyRepository
        , MoneyLogRepository $moneyLogRepository
        , MoneyTypeRepository $moneyTypeRepository
        , MoneyConfigRepository $moneyConfigRepository
        , MoneyStaticRepository $moneyStaticRepository
        , MoneyLogTypeRepository $moneyLogTypeRepository
        , PlatIncomeRepository $platIncomeRepository
        , PlatIncomeLogRepository $platIncomeLogRepository
        , ConfigRepository $configRepository
        , CronTabTaskRepository $cronTabTaskRepository
        , MoneyFreezeLogRepository $moneyFreezeLogRepository
    )
    {
        parent::__construct($registry, Money::class);
        $this->userRepository = $userRepository;
        $this->moneyRepository = $moneyRepository;
        $this->moneyLogRepository = $moneyLogRepository;
        $this->moneyTypeRepository = $moneyTypeRepository;
        $this->moneyConfigRepository = $moneyConfigRepository;
        $this->moneyStaticRepository = $moneyStaticRepository;
        $this->moneyLogTypeRepository = $moneyLogTypeRepository;
        $this->platIncomeRepository = $platIncomeRepository;
        $this->platIncomeLogRepository = $platIncomeLogRepository;
        $this->configRepository = $configRepository;
        $this->cronTabTaskRepository = $cronTabTaskRepository;
        $this->moneyFreezeLogRepository = $moneyFreezeLogRepository;
    }


    public function mod(User $user, $money, $moneyTypeId, $logTypeId, $tips = "", $param = [])
    {//通用资金添加，增加资金的时候变动余额，增加记录，改变统计信息
        $this->_em->beginTransaction();
        try {
            $moneyType = $this->moneyTypeRepository->find($moneyTypeId);
            $logType = $this->moneyLogTypeRepository->findOneBy(array("code" => $logTypeId));
            $userMoney = $this->moneyRepository->getUserMoneyByType($user, $moneyType);
            $curMoney = $userMoney->getMoney();
            //变更余额
            $userMoney->setMoney($curMoney + $money);
            $this->_em->persist($userMoney);

            //变更数据统计
            $userStatic = $this->moneyStaticRepository->getUserMoneyStaticByType($user, $moneyType, $logType);
            $curStaticMoney = $userStatic->getMoney();
            $userStatic->setMoney($curStaticMoney + $money);
            $this->_em->persist($userStatic);

            //增加流水记录
            $MoneyLog = new MoneyLog();
            $MoneyLog->setMoney($money)
                ->setUser($user)
                ->setMoneyType($moneyType)
                ->setLogType($logType)
                ->setCreateAt(new \DateTime())
                ->setTips($tips)->setParam($param);
            $this->_em->persist($MoneyLog);
            $this->_em->flush();
        } catch (\Exception $exception) {
            $this->_em->rollback();
            throw $exception;
            throw new \Exception($exception->getMessage() . "[" . $exception->getLine() . "]", 500);
        }
        $this->_em->commit();
    }

    /**
     * @param User $user
     * @param $money
     * @throws \Exception
     */
    public function addInsureMoney(User $user, $money)
    {
        return $this->mod($user, $money, self::__MONEY_JI_FEN__, 110, "积分充值", []);
    }

    public function addMoneyAndLog(User $user, $money = 0, $text = "")
    {


    }

    public function addMoney(User $user, $money)
    {
        $money = new Money();
    }

    public function addLog(User $user)
    {
        $log = new MoneyLog();
    }

    public function addStatistic(User $user, $money)
    {

    }

    public function getUserMoney(User $user)
    {
        $moneyPackage = $user->getMoney();
        $money["yue"] = 0;//1余额
        $money["bao"] = 0;//2保证金
        $money["jifen"] = 0;//3积分
        $money["xuni"] = 0;//4虚拟币
        $money["exp"] = 0;//5经验值
        $money["guquan"] = 0;//5经验值
        foreach ($moneyPackage as $moneyEntity) {
            switch ($moneyEntity->getType()->getId()) {
                case 1:
                    $key = "yue";
                    break;
                case 2:
                    $key = "bao";
                    break;
                case 3:
                    $key = "jifen";
                    break;
                case 4:
                    $key = "xuni";
                    break;
                case 5:
                    $key = "exp";
                    break;
                case 6:
                    $key = "guquan";
                    break;
                default:
                    $key = "other";
            }
            $money[$key] = $moneyEntity->getMoney();
        }
        return $money;
    }

    public function getPlatGuQuan()
    {
        $qb = $this->moneyRepository->createQueryBuilder("A");
        $qb->select(
            "SUM(A.money) as total"
        );
        $qb->where("A.type = " . (self::__MONEY_GU_QUAN__));
        if ($data = $qb->getQuery()->execute()) {
            return (float)$data[0]["total"];
        } else {
            return 0;
        }

    }

    public function modPlatMoney($money, $flag, $param, $tips, $province, $city, $area)
    {
        //创建记录
        $log = new PlatIncomeLog();
        $log->setMoney($money)
            ->setCreateAt(new \DateTime())
            ->setFlag($flag)
            ->setParam($param)
            ->setTips($tips)
            ->setProvince($province)
            ->setCity($city)
            ->setArea($area);
        $this->_em->persist($log);
        //改变当日汇总
        $platIncome = $this->platIncomeRepository->getByDay($flag);
        $platIncome->setTotal(
            $platIncome->getTotal() + $money
        );
        $this->_em->persist($platIncome);
        $this->_em->flush();
    }

    public function addPlatIncome($money, $orderId = "")
    {
        $this->modPlatMoney($money, self::__FLAG_PLAT_INCOME__, ["orderId" => $orderId], $orderId, "", "", "");
    }

    public function addBonusPool($money, $orderId = "", $province = "", $city = "", $area = "")
    {
        $this->modPlatMoney($money, self::__FLAG_BONUS_POOL__, ["orderId" => $orderId], $orderId, $province, $city, $area);
    }

    public function addAgentIncome($money, $orderId = "", $province = "", $city = "", $area = "")
    {
        $this->modPlatMoney($money, self::__FLAG_AGENT_INCOME__, ["orderId" => $orderId], $orderId, $province, $city, $area);
    }

    public function getBonusPoolByDay($day = "")
    {
        if (!$day) {
            $day = date("Y-m-d");
        }
        $platIncome = $this->platIncomeRepository->findOneBy(array("day" => $day, "flag" => self::__FLAG_BONUS_POOL__));
        if (!$platIncome) {
            $platIncome = new PlatIncome();
            $platIncome->setCreateAt(new \DateTime())
                ->setTotal(0)
                ->setDay($day)
                ->setFlag(self::__FLAG_BONUS_POOL__);
            $this->_em->persist($platIncome);
            $this->_em->flush();
        }
        return $platIncome->getTotal();
    }


    //计划任务相关

    /**
     * 理财收益计划任务_创建
     * K = CronTabLiCai
     */
    public function cronTabLiCaiCreate()
    {
        $limit = 1000;//分批数量，避免数量过大服务器超载
        $interval = 300;//单位秒，正式环境应该为一天，执行时间逻辑待定//todo
        $key = "CronTabLiCai";
        //周期信息计算
        $term = floor(time() / $interval);
        //检查周期内计划是否已经存在，存在就不处理了
        $finds = $this->cronTabTaskRepository->findBy(array("tips" => $term, "k" => $key));
        if ($finds) {
            return false;
        }


        //获取用户理财账户有余额的用户列表并写入任务
        $sort = 0;//未多线程预留的字段//todo 暂时没有什么用
        do {
            $qb = $this->moneyRepository->createQueryBuilder("A");
            $qb->select("sum(A.money) as money");
            $qb->addSelect("User.id as user");
            $qb->join("A.user", "User");
            $qb->where(
                $qb->expr()->in("A.type", [
                    MoneyService::__MONEY_XU_NI_BI__,
                    MoneyService::__MONEY_GU_QUAN__
                ])
            );
            $qb->andWhere("A.money > 0 ");
            $qb->groupBy("A.user");
            $qb->setFirstResult($sort * $limit);
            $qb->setMaxResults($limit);
            $data = $qb->getQuery()->execute();
            $count = count($data);
            if ($count > 0) {
                $task = new CronTabTask();
                $task->setK($key)
                    ->setCreateAt(new \DateTime())
                    ->setSort($sort)
                    ->setStatus(0)
                    ->setTips($term)//这里记录周期信息，如果是按天
                    ->setContext(json_encode($data));
                $this->_em->persist($task);
                $this->_em->flush();
            }
            $sort++;
        } while ($count > 0);
    }

    /**
     * 理财收益计划任务_执行
     * K = CronTabLiCai
     */
    public function cronTabLiCaiDo()
    {
        //获取理财收益后台设置的指标
        $rate = $this->configRepository->getByK("INCOME_RATE_YEAR");

        //获取用户理财账户有余额的用户列表
        $task = $this->cronTabTaskRepository->findOneBy(array(
            "k" => "CronTabLiCai",
            "status" => 0,
        ));
        $this->_em->beginTransaction();
        if (!$task) {
            return 0;
        }
        try {
            $data = json_decode($task->getContext(), true);
            foreach ($data as $row) {
                $user = $this->userRepository->find($row["user"]);
                if ($user && $row["money"] > 0) {
                    $money = round($row["money"] * $rate / 100 / 365, 6);
                    self::mod($user, $money, self::__MONEY_XU_NI_BI__, 150, "[{$task->getTips()}]增值收益", []);
                }
            }
            $task->setStatus(1)
                ->setFinishAt(new \DateTime());
            $this->_em->persist($task);
            $this->_em->flush();
        } catch (\Exception $exception) {
            $this->_em->rollback();
            throw $exception;
            //todo 这里应该记录发生错误时的信息
        }
        $this->_em->commit();
    }

    /**
     * 分红计划任务_创建
     * K = CronTabFenHong
     */
    public function crontabFenHongCreate()
    {
        $limit = 1000;//分批数量，避免数量过大服务器超载
        $interval = 300;//单位秒，正式环境应该为一天，执行时间逻辑待定//todo
        $key = "CronTabFenHong";
        //周期信息计算
        $term = floor(time() / $interval);
        //检查周期内计划是否已经存在，存在就不处理了
        $finds = $this->cronTabTaskRepository->findBy(array("tips" => $term, "k" => $key));
        if ($finds) {
            return false;
        }


        //获取用户理财账户有余额的用户列表并写入任务
        $sort = 0;//未多线程预留的字段//todo 暂时没有什么用
        do {
            $qb = $this->moneyRepository->createQueryBuilder("A");
            $qb->select("A.money");
            $qb->addSelect("User.id as user");
            $qb->join("A.user", "User");
            $qb->where("A.type = " . MoneyService::__MONEY_GU_QUAN__);
            $qb->andWhere("A.money > 0 ");
            $qb->setFirstResult($sort * $limit);
            $qb->setMaxResults($limit);
            $data = $qb->getQuery()->execute();
            $count = count($data);
            if ($count > 0) {
                $task = new CronTabTask();
                $task->setK($key)
                    ->setCreateAt(new \DateTime())
                    ->setSort($sort)
                    ->setStatus(0)
                    ->setTips($term)//这里记录周期信息，如果是按天
                    ->setContext(json_encode($data));
                $this->_em->persist($task);
                $this->_em->flush();
            }
            $sort++;
        } while ($count > 0);
    }

    /**
     * 分红收益计划任务_执行
     * K = CronTabFenHong
     */
    public function cronTabFenHongDo()
    {
        //获取理财收益后台设置的指标
        $rate = $this->configRepository->getByK("INCOME_RATE_YEAR");

        //获取用户理财账户有余额的用户列表
        $task = $this->cronTabTaskRepository->findOneBy(array(
            "k" => "CronTabFenHong",
            "status" => 0,
        ));
        $this->_em->beginTransaction();
        if (!$task) {
            return 0;
        }
        try {
            $data = json_decode($task->getContext(), true);
            $platGuquan = round(self::getPlatGuQuan(), 6);
            $platFenhong = round(self::getBonusPoolByDay(), 6);
            $rateStr = $this->configRepository->getByK("FENHONG_DAY_RATE");
            $dayRate = myExplodeNumbers($rateStr);
            foreach ($data as $row) {
                $user = $this->userRepository->find($row["user"]);
                if ($user && $row["money"] > 0) {
                    $money = self::calcUserFenHong($user, $platGuquan, $platFenhong, $dayRate);
//                    $money = 0;
                    //如果需要账务清晰,需要记录股权结余
                    self::mod($user, $money, self::__MONEY_XU_NI_BI__, 151, "[{$task->getTips()}]分红收益", []);
                }
            }
            $task->setStatus(1)
                ->setFinishAt(new \DateTime());
            $this->_em->persist($task);
            $this->_em->flush();
        } catch (\Exception $exception) {
            $this->_em->rollback();
            throw $exception;
            //todo 这里应该记录发生错误时的信息
        }
        $this->_em->commit();
    }

    public function calcUserFenHong(User $user, $platGuquan = 0, $platFenhong = 0, $dayRate = [])
    {
        //平台股权
        if (!$platGuquan) {
            $platGuquan = round(self::getPlatGuQuan(), 6);
        }
        if (!$platGuquan) {
            return 0;
        }
        //今日分红池
        if (!$platFenhong) {
            $platFenhong = round(self::getBonusPoolByDay(), 6);
        }

        if (!$dayRate) {
            $rateStr = $this->configRepository->getByK("FENHONG_DAY_RATE");
            $dayRate = myExplodeNumbers($rateStr);
        }

        //--用户分红预计
        $money = 0;
        $fenHongFreezeLogs = self::getUserFreeze($user
            , self::__MONEY_GU_QUAN__
            , self::__FREEZE_GU_QUAN__
        );
        $userFenhong = 0;
        foreach ($fenHongFreezeLogs as $log) {
            $days = floor((time() - $log->getStartAt()->getTimestamp()) / (24 * 3600));
            $rate = $dayRate[$days] ?? 1;
//            echo($rate);
            $userFenhong += $platFenhong * ($log->getMoney()) / $platGuquan * $rate;
        }
        $userFenhong = round($userFenhong, 6);
        return $userFenhong;
    }


    /**
     * @param User $user
     * @param $moneyTypeId
     * @param $flag
     * @param string $orderby
     * @return mixed|MoneyFreezeLog[]
     */
    public function getUserFreeze(User $user, $moneyTypeId, $flag, $orderby = "ASC")
    {
        $qb = $this->moneyFreezeLogRepository->createQueryBuilder("A");
        $qb->select("A")
            ->where("A.user = " . $user->getId())
            ->andWhere("A.type = " . $moneyTypeId)
            ->andWhere("A.flag = " . $flag)
            ->orderBy("A.startAt", $orderby);
        return $qb->getQuery()->execute();
    }

    public function deleteFreezeGuQuan(User $user, $money)
    {
        $list = self::getUserFreeze($user, self::__MONEY_GU_QUAN__, self::__FREEZE_GU_QUAN__, "DESC");
        foreach ($list as $row) {
            if ($money < $row->getMoney()) {
                $row->setMoney($row->getMoney() - $money);
                $this->_em->persist($row);
                $money = 0;

            } else {
                $this->_em->remove($row);
                $money -= $row->getMoney();
            }
        }
        if ($money > 0) {
            throw new \Exception("money out of range", 500);
        }
        $this->_em->flush();
    }


    public function userCash(User $user, $money)
    {
        //修改资金变动
        self::mod($user, $money * -1, self::__MONEY_XIAN_JIN__, 201, "余额提现", []);

        $card = $user->getMoneyCards()->getValues()[0] ?? null;
        //添加提现记录
        $MoneyCashLog = new MoneyCashLog();
        $MoneyCashLog->setMoney($money)
            ->setUser($user)
            ->setStatus(0)
            ->setCreateAt(new \DateTime())
            ->setMessage("")
            ->setCard($card);
        $this->_em->persist($MoneyCashLog);

        $this->_em->flush();
    }

//    public function userCashSuccess(MoneyCashLog $MoneyCashLog,$message="已付款")
//    {
//        $MoneyCashLog->setDealAt(new \DateTime())
//            ->setMessage($message)
//            ->setStatus(1)
//        ;
//        $this->_em->persist($MoneyCashLog);
//
//        $this->_em->flush();
//    }

    public function userCashFail(MoneyCashLog $MoneyCashLog, $message = "提现被拒")
    {
        $user = $MoneyCashLog->getUser();
        $money = $MoneyCashLog->getMoney();
        //修改资金变动
        self::mod($user, $money, self::__MONEY_XIAN_JIN__, 109, "提现失败返回", []);

        //修改提现记录
        $MoneyCashLog->setDealAt(new \DateTime())
            ->setMessage($message)
            ->setStatus(2);
        $this->_em->persist($MoneyCashLog);

        $this->_em->flush();
    }

    public function getUserLog(User $user, $moneyTypeId, $logTypeCode, $page = 1, $limit = 10)
    {
        $moneyType = $this->moneyTypeRepository->find($moneyTypeId);
        $logType = $this->moneyLogTypeRepository->findOneBy(array("code" => $logTypeCode));
//        $qb = $this->moneyLogRepository->createQueryBuilder("A");
        $condition = array(
            "user" => $user,
            "moneyType" => $moneyType
        );
        if ($logTypeCode) {
            $condition["logType"] = $logType;
        }
        $data = $this->moneyLogRepository->findBy(
            $condition,
            array("id" => "DESC")
            , $limit, ($page - 1) * $limit
        );
        return $data;
    }

    //------在线支付相关：创建回调订单，回调处理----start
    public function createPayOrder(User $user, $money, $param = [])
    {
        $MoneyPayOrder = new MoneyPayOrder();
        $MoneyPayOrder->setUser($user)
            ->setCreateAt(new \DateTime())
            ->setMoney($money)
            ->setParam($param)
            ->setSn(createOrderSn($user->getId()));
        $this->_em->persist($MoneyPayOrder);
        $this->_em->flush();
        return $MoneyPayOrder;
    }


    //---------------支付宝支付
    public function createPayParam($money, $payType, $cbCode,$cbStr = "",$openId = "")
    {
        switch ($payType) {
            case 100://微信支付
                $wechatPay = new WechatPay();
//                if (!$openId) {
//                    return false;
//                }
                return $wechatPay->createOrder($openId,"订单支付",$cbCode,$money,$cbStr);
            case 200://支付宝支付
                $alipay = new Alipay\Alipay();
                return $alipay->createOrder($cbCode, $money,0,"订单支付",$cbStr);
            case 500://余额支付
            default:
                return false;
        }

    }
    public function createAppPayParam($money, $payType, $cbCode,$cbStr = "",$openId = "")
    {
        switch ($payType) {
            case 100://微信支付
                $wechatPay = new WechatPay();
//                if (!$openId) {
//                    return false;
//                }
                return $wechatPay->createAppOrder($openId,"订单支付",$cbCode,$money,$cbStr);
            case 200://支付宝支付
                $alipay = new Alipay\Alipay();
                return $alipay->createOrder($cbCode, $money,0,"订单支付",$cbStr);
            case 500://余额支付
            default:
                return false;
        }

    }
    //------在线支付相关：创建回调订单，回调处理----end

}